/**
 * 
 */
package us.gaaoc.framework.webapp.controllers;


import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.faces.component.UISelectOne;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.model.DataModel;
import javax.faces.model.SelectItem;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.myfaces.component.html.ext.HtmlDataTable;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import us.gaaoc.framework.model.CaseRecord;
import us.gaaoc.framework.model.CaseRecordStatus;
import us.gaaoc.framework.model.Person;
import us.gaaoc.framework.service.TagService;
import us.gaaoc.framework.webapp.backingbeans.BaseBackingBean;
import us.gaaoc.framework.webapp.controllers.model.TableView;
import us.gaaoc.framework.webapp.util.CheckTag;
import us.gaaoc.framework.webapp.util.PagedListDataModel;

/**

 * 
 * @author artripa
 *
 */

public abstract class BaseTableController extends BaseBackingBean implements InitializingBean{

	//HTTP Session objects:
	//	$TABLE_sortDataTable = mostRecent OR initiatedDate
	//	$TABLE_order = asc or desc
	
	private CheckTag checkTag;
	private String externalCaseRecordNumber;
	private String docketId;
	private UISelectOne caseStatusInput;
	private String caseStatus;
	
	private ArrayList<SelectItem> caseStatusItems = new ArrayList<SelectItem>();

	public CheckTag getCheckTag() {
		return checkTag;
	}
	
	@Override
	public void afterPropertiesSet() throws Exception {
		checkTag = new CheckTag(tagService, this.getPerson(),this.getModelFacade());		
	}
	
	@Autowired
	private TagService tagService;
	
	public TagService getTagService() {
		return tagService;
	}
	
	public void setTagService(TagService tagService) {
						
		this.tagService = tagService;
	}	
	
	@Transactional
	public String toggleTag() {		
		ExternalContext context = FacesContext.getCurrentInstance().getExternalContext();
		HttpServletRequest request = (HttpServletRequest)context.getRequest();
		String caseRecordUuid = request.getParameter("caseRecord");		
		tagService.toggle((CaseRecord) this.getModelFacade().getSession().get(CaseRecord.class,caseRecordUuid),this.getPerson());	
		this.checkTag.invalidateCacheEntry(caseRecordUuid);		
		return "";
	}
	
	private static final long serialVersionUID = 1L;
	private Logger log = Logger.getLogger(BaseTableController.class);
	/**
	 * @param dataTable the dataTable to set
	 */
	public void setDataTable(HtmlDataTable dataTable) {
		this.dataTable = dataTable;
	}
	
	/**
	 * @return the dataTable
	 */
	public HtmlDataTable getDataTable() {
		
		if(dataTable == null)
		{
		
			HtmlDataTable dataTable = new HtmlDataTable();
			dataTable.setRows(10);
			setDataTable(dataTable);
		}
		return dataTable;
	}
	
	private HtmlDataTable dataTable; /** jsf data table binding */
	
	
	public String sortBy()
	{
		String sortBy = "";
		
		
		Object o = this.getHttpSession().getAttribute(tableView.getId() + "_sortDataTable");
		
		if(o != null)
		{
			sortBy = (String) this.getSessionMap().get(tableView.getId() + "_sortDataTable");
		}else{
			sortBy = "mostRecent";
			setSortBy(sortBy);
		}
		return sortBy;
	}
	
	public void setSortBy(String sort)
	{
		this.getSessionMap().put(tableView.getId() + "_sortDataTable",sort);
	}
	
	public void setOrder(String order) {
		this.getSessionMap().put(tableView.getId() + "_order",order);
	}
	
	public String order()
	{
		String order = "";
		if(this.getSessionMap().containsKey(tableView.getId() + "_order"))
		{
			
			order = (String) this.getSessionMap().get(tableView.getId() + "_order");
		}else{
			order = "desc";
			this.setOrder(order);
		}
		return order;
	}
	
	private List<TableView> tableList; /** The list of views accessible by the given user */
	private TableView tableView; /** The TableView object */	
	private String displayTable; /** The ID of the table to display */
	private String displayTableReadable; /** The readable name of the table */
	private List<CaseRecord> data; /** The data backing the table */
	
	private boolean filterPersonalCases; /** flag to tell whether we are filtering by personal cases */	
	
	public DataModel getData() {
		switchTable();
		int totalListSize = 0;
		if((getExternalCaseRecordNumber()!=null && !this.getExternalCaseRecordNumber().equalsIgnoreCase("")) ||(getDocketId()!=null && !this.getDocketId().equalsIgnoreCase(""))||(getCaseStatus()!=null && !this.getCaseStatus().equalsIgnoreCase(""))){
			totalListSize = getSearchDataSize();
			populateSearchData();	
		}else{
			totalListSize = getDataSize(tableView);
			populateData();					
		}
		
		PagedListDataModel dataModel = new PagedListDataModel(data, totalListSize, this.getDataTable().getRows());
		
		return dataModel;
	}
	
	
	private void populateSearchData() {		
		data = getSearchLazyData(this.getDataTable().getFirst(),this.getDataTable().getRows()).list();
	}

	private Query getSearchLazyData(int start, int max) {
		String query = "select DISTINCT {cr.*} %s from CaseRecord cr, CaseRecordStatus crs, LocalCaseType lct,LocalCaseCategory lcc, CaseRecordHistory crh," +
				"CaseRecordEventType cret,PersonParticipants pp,Person p,CaseRecordIdExternalCaseRecordNumber crextr %s where cr.clearFilingFlag = false " +
				"AND cr.deletedFilingFlag = false AND cr.archivedFlag = false AND cr.statusCode = crs.code AND cr.localCaseTypeCode = lct.code " +
				"AND cr.localCaseCategoryCode = lcc.code AND cr.uuid = crh.caseRecordUuid AND crh.eventTypeCode=cret.code AND cr.uuid = pp.caseRecordUuid " +
				"AND pp.relationshipCode = 2 AND pp.personUuid = p.uuid AND crextr.caseRecordUuid = cr.uuid AND cr.courtUuid = :courtUuid %s %s order by %s %s LIMIT %s,%s";		
		String orderBy;
		List<String> selections = new ArrayList<String>();
		List<String> joins = new ArrayList<String>();
		List<String> predicates = new ArrayList<String>();
		String groupBy = "";
			
		if(this.sortBy().equals("mostRecent"))
		{
			selections.add("MAX(crh.eventDateTime)");
			orderBy = "crh.eventDateTime";				
			groupBy = " GROUP BY crh.caseRecordUuid ";
		}else if(this.sortBy().equals("initiatedDate"))
		{
			orderBy = "instantiationDateTime";
		}else{
			throw new UnsupportedOperationException();
		}
		
		if(getCaseStatus()!=null && !this.getCaseStatus().equalsIgnoreCase("")){
			predicates.add("crs.code = :casStatusCode");			
		}		
		
		if(this.getExternalCaseRecordNumber()!=null && !this.getExternalCaseRecordNumber().equalsIgnoreCase("")){
			predicates.add("crextr.externalCaseRecordNumber = :externalCaseRecordNumber");
		}
		
		if(this.getDocketId()!=null && !this.getDocketId().equalsIgnoreCase("")){
			predicates.add("caseTrackingId = :caseTrackingId");			
		}
		
		// build query
		String joinsStr =  joins.isEmpty() ? "" :  "," + StringUtils.join(joins.iterator(),", ");
		String predicateStr = predicates.isEmpty() ? "" : " AND " + StringUtils.join(predicates.iterator()," AND ");
		String selStr = selections.isEmpty()? "" : "," + StringUtils.join(selections.iterator(),", ");
		
		query = String.format(query,selStr,joinsStr,predicateStr,groupBy, orderBy,this.order(),Integer.toString(start),Integer.toString(max));
		
		Session session = this.getModelFacade().getSession();	
		
		Query qobj = session.createSQLQuery(query)
			.addEntity("cr",CaseRecord.class)
			.setString("courtUuid",this.getCourtUuid());
			
		if(this.getExternalCaseRecordNumber()!=null && !this.getExternalCaseRecordNumber().equalsIgnoreCase("")){
			qobj = qobj.setString("externalCaseRecordNumber", this.getExternalCaseRecordNumber());			
		}
		
		if(this.getDocketId()!=null && !this.getDocketId().equalsIgnoreCase("")){
			qobj = qobj.setString("caseTrackingId", this.getDocketId());
		}
		
		if(getCaseStatus()!=null && !this.getCaseStatus().equalsIgnoreCase("")){
			qobj = qobj.setInteger("casStatusCode", Integer.parseInt(this.getCaseStatus()));			
		}	
		
		return qobj;

	}

	private int getSearchDataSize() {
		String query = "select count(DISTINCT cr.uuid) from CaseRecord cr, CaseRecordStatus crs, LocalCaseType lct,LocalCaseCategory lcc, CaseRecordHistory crh," +
				"CaseRecordEventType cret,PersonParticipants pp,Person p,CaseRecordIdExternalCaseRecordNumber crextr %s where cr.clearFilingFlag = false " +
				"AND cr.deletedFilingFlag = false AND cr.archivedFlag = false AND cr.statusCode = crs.code AND cr.localCaseTypeCode = lct.code " +
				"AND cr.localCaseCategoryCode = lcc.code AND cr.uuid = crh.caseRecordUuid AND crh.eventTypeCode=cret.code AND cr.uuid = pp.caseRecordUuid " +
				"AND pp.relationshipCode = 2 AND pp.personUuid = p.uuid AND crextr.caseRecordUuid = cr.uuid AND cr.courtUuid = :courtUuid %s ";		
		List<String> joins = new ArrayList<String>();
		List<String> predicates = new ArrayList<String>();
		
		if(getCaseStatus()!=null && !this.getCaseStatus().equalsIgnoreCase("")){
			predicates.add("crs.code = :casStatusCode");			
		}	
		
		if(this.getExternalCaseRecordNumber()!=null && !this.getExternalCaseRecordNumber().equalsIgnoreCase("")){
			predicates.add("crextr.externalCaseRecordNumber = :externalCaseRecordNumber");
		}
		
		if(this.getDocketId()!=null && !this.getDocketId().equalsIgnoreCase("")){
			predicates.add("cr.caseTrackingId = :caseTrackingId");			
		}	
			
		// build query
		String joinsStr =  joins.isEmpty() ? "" :  "," + StringUtils.join(joins.iterator(),", ");
		String predicateStr = predicates.isEmpty() ? "" : " AND " + StringUtils.join(predicates.iterator()," AND ");
		
		query = String.format(query,joinsStr,predicateStr);

		Session session = this.getModelFacade().getSession();	
		
		Query qobj = session.createSQLQuery(query)			
			.setString("courtUuid",this.getCourtUuid());
		
		
		if(this.getExternalCaseRecordNumber()!=null && !this.getExternalCaseRecordNumber().equalsIgnoreCase("")){
			qobj = qobj.setString("externalCaseRecordNumber", this.getExternalCaseRecordNumber());			
		}
		
		if(this.getDocketId()!=null && !this.getDocketId().equalsIgnoreCase("")){
			qobj = qobj.setString("caseTrackingId", this.getDocketId());
		}	
		if(getCaseStatus()!=null && !this.getCaseStatus().equalsIgnoreCase("")){
			qobj = qobj.setInteger("casStatusCode", Integer.parseInt(this.getCaseStatus()));			
		}	
			
		return ((BigInteger)qobj.uniqueResult()).intValue();
	}

	/* Utility functions */		
	protected TableView findtable(String id)
	{
		for(TableView tv : tableList)
		{
			if(tv.getId().equals(id)) return tv;
		}
		return null;
	}
	
	private void switchTable() {
		
		if(displayTable == null)
		{
			displayTable = "default";
		}
		tableView = (TableView) findtable(displayTable);
		if(tableView == null) {
			displayTableReadable = "";
		}else{
			displayTableReadable = tableView.getDescription();
		}
	}
		
	
	private void populateData() {
		//if we don't populate data even when non-null, we get
		// a pagination bug.
		data = getLazyData(tableView,this.getDataTable().getFirst(),this.getDataTable().getRows()).list();
	}
	
	
	private void readIsPersonalCases()
	{
		String str = (String) this.getSessionMap().get("filterPersonalCases");
		if(str == null || str.equalsIgnoreCase(""))
		{
			this.setFilterPersonalCases(false);
			this.getSessionMap().put("filterPersonalCases",Boolean.toString(this.isFilterPersonalCases()));
		}else{
			this.setFilterPersonalCases(Boolean.parseBoolean(str));
		}
	}
	
	public void togglePersonalCases()
	{		
		this.setFilterPersonalCases(!filterPersonalCases);
		this.getSessionMap().put("filterPersonalCases",Boolean.toString(this.isFilterPersonalCases()));
	}
	
	public BaseTableController()
	{	
		readIsPersonalCases();		
		setDisplayTable((String) this.getSessionMap().get("displayTable"));				
	}
	
	
	public String sortByMostRecent()
	{		
		if(this.sortBy().equals("mostRecent"))
		{
			this.setOrder( this.order().equals("asc") ? "desc" : "asc" );
		}else{
			this.setOrder("desc");
			this.setSortBy("mostRecent");
		}

		return "";
	}
	
	public String sortByInitiatedDate()
	{
		if(this.sortBy().equals("initiatedDate"))
		{
			this.setOrder( this.order().equals("asc") ? "desc" : "asc" );
		}else{
			this.setOrder("desc");
			this.setSortBy("initiatedDate");
		}

		return "";
	}

	/** 
	 * Gets the current size of a table 
	 * @param tv
	 * @return
	 * @throws ClassNotFoundException 
	 * @throws HibernateException 
	 */
	public int getDataSize(TableView tv)
	{
		switchTable();
		//return ((Integer) getLazyData(tv).setProjection(Projections.count("uuid")).uniqueResult()).intValue();
		//return ((Long) getLazyData(tv,true,-1,-1).uniqueResult()).intValue();
		return getDataSize_impl(tv);
	}

	public abstract int getRole();
	
	public void setFilterPersonalCases(boolean filterPersonalCases) {
		this.filterPersonalCases = filterPersonalCases;
	}

	public boolean isFilterPersonalCases() {
		return filterPersonalCases;
	}
	

	public String getRowClasses() {
		populateData();
	    StringBuilder sb = new StringBuilder();
	    
	    for (Object data2 : this.data){
	    	CaseRecord cr = (CaseRecord)data2;
	        sb.append(cr.isValid() ? "show," : "hide,");
	    }
	    return sb.toString();
	}
	

	public String getDisplayTable() {
		return displayTable;
	}

	public void setDisplayTable(String displayTable) {
		this.displayTable = displayTable;
	}

	
	public List<TableView> getTableList() {
		return tableList;
	}

	public void setTableList(List<TableView> tableList) {
		this.tableList = tableList;
	}	
	
	protected String getCourtUuid() {
		return (String) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("courtUuid");
	}

	protected Person getPerson() {
		return ((Person)FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("person"));
	}

	public String getDisplayTableReadable() {
		switchTable();
		return this.displayTableReadable;
	}
	
	
	public boolean getHideCaseDetails() {
		if(tableView == null) return false;
		return tableView.getCaseDetail();
	}
	

	
	@SuppressWarnings("unchecked")
	/**
	 * Action which changes the table being given
	 * @requestparam displayTable The display tbale ID
	 */
	public String updateDisplayTable()
	{
		FacesContext context = FacesContext.getCurrentInstance();
		String value = (String) context.getExternalContext().getRequestParameterMap().get("displayTable");
		this.getSessionMap().put("displayTable",value);		
		return "stay";
	}
	
	
	
	
	/**
	 * Gets the data from the tables, using pagination to optimize query time.
	 * 
	 * @param tv The TableView object which points to the subtable
	 * @param start The first row we are asking for.
	 * @param max The final row we are asking for.
	 * @return The Query object, which builds the final query.
	 */
	/* TODO: move Hibernate/Database access to the database layer */
	public Query getLazyData(TableView tv,int start,int max)
	{
				
		String query = "select DISTINCT {cr.*} %s from caseRecord cr %s where courtUuid = :courtUuid %s %s order by %s %s LIMIT %s,%s";		
		String orderBy;
		List<String> selections = new ArrayList<String>();
		List<String> joins = new ArrayList<String>();
		List<String> predicates = new ArrayList<String>();
		String groupBy = "";
		
		if(tv.isTagged())
		{
			joins.add("caseRecordTags crt");
			predicates.add("crt.caseRecordUuid = cr.uuid");
			predicates.add("crt.personUuid = :person");
		}else{
			predicates.add("( statusCode = :st1 or statusCode = :st2 or statusCode = :st3 or statusCode = :st4 or statusCode = :st5 )");
		}
		
		predicates.add("deletedFilingFlag = 0");
		predicates.add("clearFilingFlag = 0");
		predicates.add("archivedFlag = 0");
		
		if(this.sortBy().equals("mostRecent"))
		{			
			joins.add("caseRecordHistory crh");
			selections.add("MAX(crh.eventDateTime)");
			predicates.add("crh.caseRecordUuid = cr.uuid");
			orderBy = "MAX(crh.eventDateTime)";				
			groupBy = " GROUP BY cr.uuid ";
		}else if(this.sortBy().equals("initiatedDate"))
		{
			orderBy = "instantiationDateTime";
		}else{
			throw new UnsupportedOperationException();
		}
		
		if(this.isFilterPersonalCases())
		{
			joins.add("personParticipants pp");
			predicates.add("pp.caseRecordUuid = cr.uuid");
			predicates.add("pp.relationshipCode = :role");
			predicates.add("pp.personUuid = :person");			
		}

		
	
		Map<String,Object> additionalParams = new HashMap<String, Object>();		
		additionalQuery(joins, predicates,additionalParams);
		
	
		
		// build query
		String joinsStr =  joins.isEmpty() ? "" :  "," + StringUtils.join(joins.iterator(),", ");
		String predicateStr = predicates.isEmpty() ? "" : " AND " + StringUtils.join(predicates.iterator()," AND ");
		String selStr = selections.isEmpty()? "" : "," + StringUtils.join(selections.iterator(),", ");
		
		query = String.format(query,selStr,joinsStr,predicateStr,groupBy, orderBy,this.order(),Integer.toString(start),Integer.toString(max));
		
		Session session = this.getModelFacade().getSession();	
		
		Query qobj = session.createSQLQuery(query)
			.addEntity("cr",CaseRecord.class)
			.setString("courtUuid",this.getCourtUuid());
		
		if(! tv.isTagged()) {				
			qobj = qobj.setInteger("st1",tv.getStatusCode1())
					.setInteger("st2",tv.getStatusCode2())
					.setInteger("st3",tv.getStatusCode3())
					.setInteger("st4",tv.getStatusCode4())
					.setInteger("st5",tv.getStatusCode5());
		}
		
		for(Entry<String,Object> param : additionalParams.entrySet())
		{
			qobj = qobj.setParameter(param.getKey(),param.getValue());
		}
				
		if(tv.isTagged())
		{
			qobj = qobj.setString("person",this.getPerson().getUuid());
		}
				
		if(this.isFilterPersonalCases())
		{
			qobj = qobj.setString("person",this.getPerson().getUuid())
				.setInteger("role",this.getRole());
		}
		
		return qobj;
	}

	public void additionalQuery(List<String> joins, List<String> predicates,Map<String,Object> additionalParams) 
	{
		// overridable by extended classes
	}
	
	public int getDataSize_impl(TableView tv) {
		
		String query = "select count(DISTINCT cr.uuid) from caseRecord cr %s where courtUuid = :courtUuid %s ";		
		List<String> joins = new ArrayList<String>();
		List<String> predicates = new ArrayList<String>();

		if(tv.isTagged())
		{
			joins.add("caseRecordTags crt");
			predicates.add("crt.caseRecordUuid = cr.uuid");
			predicates.add("crt.personUuid = :person");
		}else{			
			predicates.add("( statusCode = :st1 or statusCode = :st2 or statusCode = :st3 or statusCode = :st4 or statusCode = :st5 )");			
		}			
		
		predicates.add("deletedFilingFlag = 0");
		predicates.add("clearFilingFlag = 0");
		predicates.add("archivedFlag = 0");
				
		if(this.isFilterPersonalCases())
		{
			joins.add("personParticipants pp");
			predicates.add("pp.caseRecordUuid = cr.uuid");
			predicates.add("pp.relationshipCode = :role");
			predicates.add("pp.personUuid = :person");  
		}
	
		Map<String,Object> additionalParams = new HashMap<String, Object>();		
		additionalQuery(joins, predicates,additionalParams);
		
		// build query
		String joinsStr =  joins.isEmpty() ? "" :  "," + StringUtils.join(joins.iterator(),", ");
		String predicateStr = predicates.isEmpty() ? "" : " AND " + StringUtils.join(predicates.iterator()," AND ");
		
		query = String.format(query,joinsStr,predicateStr);

		Session session = this.getModelFacade().getSession();	
		
		Query qobj = session.createSQLQuery(query)			
			.setString("courtUuid",this.getCourtUuid());
		
		if(! tv.isTagged()) {				
			qobj = qobj.setInteger("st1",tv.getStatusCode1())
					.setInteger("st2",tv.getStatusCode2())
					.setInteger("st3",tv.getStatusCode3())
					.setInteger("st4",tv.getStatusCode4())
					.setInteger("st5",tv.getStatusCode5());
		}
		
		for(Entry<String,Object> param : additionalParams.entrySet())
		{
			qobj = qobj.setParameter(param.getKey(),param.getValue());
		}
		
		if(tv.isTagged())
		{
			qobj = qobj.setString("person",this.getPerson().getUuid());
		}
		
		if(this.isFilterPersonalCases())
		{
			qobj = qobj	.setString("person",this.getPerson().getUuid())	
						.setInteger("role",this.getRole());
		}
		
		return ((BigInteger)qobj.uniqueResult()).intValue();			
	}

	public String getExternalCaseRecordNumber() {
		return externalCaseRecordNumber;
	}

	public void setExternalCaseRecordNumber(String externalCaseRecordNumber) {
		this.externalCaseRecordNumber = externalCaseRecordNumber;
	}

	public String getDocketId() {
		return docketId;
	}

	public void setDocketId(String docketId) {
		this.docketId = docketId;
	}

	public UISelectOne getCaseStatusInput() {
		return caseStatusInput;
	}

	public void setCaseStatusInput(UISelectOne caseStatusInput) {
		this.caseStatusInput = caseStatusInput;
	}

	public String getCaseStatus() {
		return caseStatus;
	}

	public void setCaseStatus(String caseStatus) {
		this.caseStatus = caseStatus;
	}

	public ArrayList<SelectItem> getCaseStatusItems() {
		// Initialize Array List if null
		if (caseStatusItems == null) {
			caseStatusItems = new ArrayList<SelectItem>();
		}
		caseStatusItems.clear();
		this.caseStatusItems.add(0,new SelectItem(""));
		List<CaseRecordStatus> caseRecordStatusDescriptionList= this.getModelFacade().getCaseRecordStatusList();
		
		int i = caseRecordStatusDescriptionList.size();
		CaseRecordStatus cs;
		for (int j = 0; j < i; j++) {
			cs = caseRecordStatusDescriptionList.get(j);
			caseStatusItems.add(new SelectItem(new Integer(cs
					.getCode()).toString(), cs.getDescription()));
		}
			
				
		return caseStatusItems;
	}

	public void setCaseStatusItems(ArrayList<SelectItem> caseStatusItems) {
		this.caseStatusItems = caseStatusItems;
	}
	
	
}
